﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

namespace HobSoft.SoccerBet2014.Domain.Implementation
{
    using Models.Core;
    public class BinaryContext
    {
        private static string binCacheFileFormat = "Mod.{0}.bin";
        private static string xmlCacheFileFormat = "Mod.{0}.xml";
        private readonly ConcurrentDictionary<Type, IDictionary> BinaryModelCache = new ConcurrentDictionary<Type, IDictionary>();

        private BinaryContext()
        {

        }
        private static BinaryFormatter Transfer = new BinaryFormatter();
        private static BinaryContext Context = new BinaryContext();
        public static BinaryContext Current
        {
            get
            {
                return Context;
            }
        }

        private string storeFolder = Path.Combine(System.Environment.CurrentDirectory, "Cache");


        protected virtual ConcurrentDictionary<K, T> GetBinFileObject<T, K>() where T : class,IModel
        {
            string file = string.Format(binCacheFileFormat, typeof(T).Name);
            string filePath = Path.Combine(storeFolder, file);
            if (!Directory.Exists(storeFolder))
                Directory.CreateDirectory(storeFolder);
            FileStream fStream = new FileStream(filePath, FileMode.OpenOrCreate);
            BinaryReader bReader = new BinaryReader(fStream, Encoding.Unicode);
            var buffer = bReader.ReadBytes((int)fStream.Length);
            bReader.Close();
            fStream.Close();
            if (buffer.Length > 0)
            {
                MemoryStream stream = new MemoryStream(buffer);
                var result = Transfer.Deserialize(stream) as ConcurrentDictionary<K, T>;
                if (result == null)
                    result = new ConcurrentDictionary<K, T>();
                return result;
            }
            else
            {
                return new ConcurrentDictionary<K, T>();
            }
        }

        protected virtual void StoreBinFile<T, K>(ConcurrentDictionary<K, T> serObject)
        {
            string file = string.Format(binCacheFileFormat, typeof(T).Name);
            string filePath = Path.Combine(storeFolder, file);
            if (!Directory.Exists(storeFolder))
                Directory.CreateDirectory(storeFolder);

            MemoryStream stream = new MemoryStream();
            Transfer.Serialize(stream, serObject);
            var buffer = stream.GetBuffer();
            FileStream fStream = new FileStream(filePath, FileMode.Create);
            BinaryWriter bWriter = new BinaryWriter(fStream, Encoding.Unicode);

            bWriter.Write(buffer);
            bWriter.Flush();
            bWriter.Close();
            fStream.Close();
        }

        protected virtual void StoreXmlFile<T, K>(ConcurrentDictionary<K, T> serObject)
        {
            string file = string.Format(xmlCacheFileFormat, typeof(T).Name);
            string filePath = Path.Combine(storeFolder, file);
            if (!Directory.Exists(storeFolder))
                Directory.CreateDirectory(storeFolder);


            System.Xml.XmlWriter writer=System.Xml.XmlWriter.Create(filePath);
            try
            {
                var list = serObject.Values.ToList();
                System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(list.GetType());
                ser.Serialize(writer, list);
                writer.Flush();
                writer.Close();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public void SetConfig(string _storeFolder)
        {
            storeFolder = _storeFolder;
            BinaryModelCache.Clear();
        }


        private object _syncReadLock = new object();
        private object _syncWriteLock = new object();
        public ConcurrentDictionary<K, T> Get<T, K>() where T : class,IModel
        {
            var type = typeof(T);
            if (!BinaryModelCache.ContainsKey(type))
                Initialize<T, K>();
            IDictionary val;
            BinaryModelCache.TryGetValue(type, out val);
            return val as ConcurrentDictionary<K, T>;
        }

        public void Submit<T, K>() where T : class,IModel
        {
            var type = typeof(T);
            if (!BinaryModelCache.ContainsKey(type))
                return;
            IDictionary val;
            BinaryModelCache.TryGetValue(type, out val);
            var models = val as ConcurrentDictionary<K, T>;
            if (models == null) return;
            lock (_syncWriteLock)
            {
                StoreBinFile<T, K>(models);
            }
        }

        public void SubmitXml<T, K>() where T : class,IModel
        {
            var type = typeof(T);
            if (!BinaryModelCache.ContainsKey(type))
                return;
            IDictionary val;
            BinaryModelCache.TryGetValue(type, out val);
            var models = val as ConcurrentDictionary<K, T>;
            if (models == null) return;
            lock (_syncWriteLock)
            {
                StoreXmlFile<T, K>(models);
            }
        }

        public void Initialize<T, K>(Func<T, bool> clear) where T : class,IModel
        {
            var type = typeof(T);
            lock (_syncReadLock)
            {
                if (!BinaryModelCache.ContainsKey(type))
                {
                    var collection = GetBinFileObject<T, K>();
                    BinaryModelCache.TryAdd(type, collection);
                    var clearQuery = collection.Where(c => clear(c.Value)).Select(c => c.Key).ToArray();
                    foreach (var key in clearQuery)
                    {
                        T del;
                        collection.TryRemove(key, out del);
                    }
                }
            }
        }

        public void Initialize<T, K>() where T : class,IModel
        {
            var type = typeof(T);
            lock (_syncReadLock)
            {
                if (!BinaryModelCache.ContainsKey(type))
                {
                    var collection = GetBinFileObject<T, K>();
                    BinaryModelCache.TryAdd(type, collection);
                }
            }
        }
    }
}
